package cmd

import (
	"bytes"
	"context"
	"fmt"
	"image"
	"io/ioutil"
	"os"

	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/cdproto/network"
	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
	"github.com/makiuchi-d/gozxing"
	"github.com/makiuchi-d/gozxing/qrcode"
	goQrcode "github.com/skip2/go-qrcode"
)

func ExistNode(sel string, exist *bool, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return func(ctx context.Context) error {
		var nodes []*cdp.Node
		if err := chromedp.Run(ctx,
			FindNodes(sel, &nodes, opts...),
		); err != nil {
			slog.Error("ExistNode错误:", err)
			return err
		}
		*exist = len(nodes) > 0
		return nil
	}
}

func FindNodes(sel string, nodes *[]*cdp.Node, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return func(ctx context.Context) error {
		if err := chromedp.Run(ctx,
			chromedp.Nodes(sel, nodes, append(opts, chromedp.AtLeast(0), chromedp.ByQueryAll)...),
		); err != nil {
			slog.Error("FindNodes错误:", err)
			return err
		}
		return nil
	}
}

func IfPresent(sel string, trueTasks chromedp.Tasks, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return IfPresentOrElse(sel, trueTasks, Sequence(), opts...)
}

func IfPresentOrElse(sel string, trueTasks chromedp.Tasks, falseTasks chromedp.Tasks, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return func(ctx context.Context) error {
		var nodes []*cdp.Node
		if err := chromedp.Run(ctx, Sequence(
			chromedp.Nodes(sel, &nodes, append(opts, chromedp.AtLeast(0), chromedp.ByQueryAll)...),
			chromedp.ActionFunc(func(ctx context.Context) error {
				hasPresent := len(nodes) > 0
				slog.Debugf("hasPresent=>%v", hasPresent)
				// if hasPresent {
				// 	// 既然呈现就不需要在外部显示指定等待waitVisible的task
				// 	trueTasks = append(chromedp.Tasks{chromedp.WaitVisible(sel, chromedp.ByID)}, trueTasks)
				// }
				return chromedp.Run(ctx, IfOrElse(hasPresent, trueTasks, falseTasks))
			}),
		)); err != nil {
			slog.Error("CheckPresentBranch错误:", err)
			return err
		}
		return nil
	}
}

func IfPresentWithNode(sel string, nodes *[]*cdp.Node, trueTasks chromedp.Tasks, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return IfPresentOrElseWithNode(sel, nodes, trueTasks, Sequence(), opts...)
}

func IfPresentOrElseWithNode(sel string, nodes *[]*cdp.Node, trueTasks chromedp.Tasks, falseTasks chromedp.Tasks, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return func(ctx context.Context) error {
		if err := chromedp.Run(ctx, Sequence(
			chromedp.Nodes(sel, nodes, append(opts, chromedp.AtLeast(0), chromedp.ByQueryAll)...),
			chromedp.ActionFunc(func(ctx context.Context) error {
				hasPresent := len(*nodes) > 0
				slog.Debugf("hasPresent=>%v", hasPresent)
				// if hasPresent {
				// 	// 既然呈现就不需要在外部显示指定等待waitVisible的task
				// 	trueTasks = append(chromedp.Tasks{chromedp.WaitVisible(sel, chromedp.ByID)}, trueTasks)
				// }
				return chromedp.Run(ctx, IfOrElse(hasPresent, trueTasks, falseTasks))
			}),
		)); err != nil {
			slog.Error("CheckPresentBranch错误:", err)
			return err
		}
		return nil
	}
}

func IfHasText(sel string, trueTasks chromedp.Tasks, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return IfHasTextOrElse(sel, trueTasks, Sequence(), opts...)
}
func IfHasTextOrElse(sel string, trueTasks chromedp.Tasks, falseTasks chromedp.Tasks, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return func(ctx context.Context) error {
		var text string
		if err := chromedp.Run(ctx, Sequence(
			chromedp.Text(sel, &text, append(opts, chromedp.NodeVisible, chromedp.ByQueryAll)...),
			chromedp.ActionFunc(func(ctx context.Context) error {
				hasText := len(text) > 0
				slog.Debugf("hasText=>%v", hasText)
				return chromedp.Run(ctx, IfOrElse(hasText, trueTasks, falseTasks))
			}),
		)); err != nil {
			slog.Error("CheckPresentBranch错误:", err)
			return err
		}
		return nil
	}
}

type MatchPredictor func(context.Context) bool

func IfP(pred MatchPredictor, trueTasks chromedp.Tasks, falseTasks chromedp.Tasks, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return IfOrElseP(pred, trueTasks, Sequence())
}

func IfOrElseP(pred MatchPredictor, trueTasks chromedp.Tasks, falseTasks chromedp.Tasks, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return func(ctx context.Context) error {
		matched := pred(ctx)
		return chromedp.Run(ctx, IfOrElse(matched, trueTasks, falseTasks))
	}
}

func If(matched bool, trueTasks chromedp.Tasks) chromedp.Action {
	return IfOrElse(matched, trueTasks, Sequence())
}

func IfOrElse(matched bool, trueTasks chromedp.Tasks, falseTasks chromedp.Tasks, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return func(ctx context.Context) error {
		var tasks chromedp.Tasks
		if matched {
			// 既然呈现就不需要在外部显示指定等待waitVisible的task
			tasks = trueTasks
		} else {
			tasks = falseTasks
		}
		if len(tasks) == 0 {
			tasks = append(tasks, Noop())
		}
		return chromedp.Run(ctx, tasks)
	}
}

type NoopAction struct{}

func (s NoopAction) Do(ctx context.Context) error {
	if isDebug {
		fmt.Println("noop...")
	}
	return nil
}

func Noop() chromedp.Action {
	return NoopAction{}
}

func Sequence(actions ...chromedp.Action) chromedp.Tasks {
	return actions
}

type IterNodeFilter interface {
	Filter(context.Context, int, *cdp.Node) bool
}
type IterNodeFilterFunc func(context.Context, int, *cdp.Node) bool

func (f IterNodeFilterFunc) Filter(ctx context.Context, idx int, node *cdp.Node) bool {
	return f(ctx, idx, node)
}

type IterNodeAction interface {
	Do(context.Context, int, *cdp.Node) chromedp.Action
}
type IterNodeNextFunc func(context.Context, int, *cdp.Node) chromedp.Action

func (f IterNodeNextFunc) Do(ctx context.Context, idx int, node *cdp.Node) chromedp.Action {
	return f(ctx, idx, node)
}

type IterDoneFunc = func(context.Context, bool) chromedp.Action

func Iter(sel string, next IterNodeAction, done IterDoneFunc, filters ...IterNodeFilter) chromedp.ActionFunc {
	return func(ctx context.Context) (err error) {
		var nodes []*cdp.Node
		var matchAll bool
		return chromedp.Run(ctx,
			chromedp.Nodes(sel, &nodes, chromedp.AtLeast(0), chromedp.ByQueryAll),
			chromedp.ActionFunc(func(ctx context.Context) error {
				var tasks chromedp.Tasks
				// pageCount := len(nodes)
				for i := range nodes {
					pass := true
					for _, ft := range filters {
						if pass = pass && ft.Filter(ctx, i, nodes[i]); !pass {
							break
						}
					}
					if pass {
						fmt.Printf("pass filter node(%d)\n", i)
						tasks = append(tasks, next.Do(ctx, i, nodes[i]))
					} else {
						fmt.Printf("not pass filter node(%d)\n", i)
					}
				}
				matchAll = len(tasks) == len(nodes)
				return chromedp.Run(ctx, tasks)
			}),
			chromedp.ActionFunc(func(ctx context.Context) error {
				return chromedp.Run(ctx, done(ctx, matchAll))
			}),
		)
	}
}

type BreakChecker interface {
	Check(context.Context) bool
}

type BreakCheckFunc func(context.Context) bool

func (f BreakCheckFunc) Check(ctx context.Context) bool {
	return f(ctx)
}

func Loop(breakChecker BreakChecker, action chromedp.Action) chromedp.ActionFunc {
	return func(ctx context.Context) (err error) {
		for {
			err = chromedp.Run(ctx, action)
			if err != nil {
				slog.Errorf("loop action err:%s", err)
				break
			}
			if breakChecker.Check(ctx) {
				slog.Debugf("normal check break to exit loop...")
				break
			}
		}
		return
	}
}

// make sure sel exist and visible
func ScreenshotElemAndDump(sel, dumpFile string, bh BufferHolder, text *string, opts ...chromedp.QueryOption) chromedp.ActionFunc {
	return func(ctx context.Context) (err error) {
		return chromedp.Run(ctx,
			// chromedp.Screenshot(sel, bh.BufferAddr(), chromedp.NodeVisible, chromedp.ByID),
			chromedp.Screenshot(sel, bh.BufferAddr(), append(opts, chromedp.NodeVisible, chromedp.ByQuery)...),
			chromedp.ActionFunc(func(ctx context.Context) error {
				if err := bh.Dump(dumpFile); err != nil {
					return err
				}
				*text = bh.Extract(dumpFile)
				return nil
			}),
		)
	}
}

// 获取二维码的过程(小程序)
func getQRCode(sel string) chromedp.ActionFunc {
	return func(ctx context.Context) (err error) {
		// 1. 用于存储图片的字节切片
		var code []byte
		slog.Info("getCode..")
		// 2. 截图
		// 注意这里需要注明直接使用ID选择器来获取元素（chromedp.ByID）
		// `#app > div > div.main-content > div > div > div.scan-side > img`
		if err = chromedp.Screenshot(sel, &code).Do(ctx); err != nil {
			return
		}
		// 3. 把二维码输出到标准输出流
		if err = printQRCode(code); err != nil {
			return err
		}
		return
	}
}

// 输出二维码
func printQRCode(code []byte) (err error) {
	// 1. 因为我们的字节流是图像，所以我们需要先解码字节流
	img, _, err := image.Decode(bytes.NewReader(code))
	if err != nil {
		return
	}
	slog.Info("decode..")
	// 2. 然后使用gozxing库解码图片获取二进制位图
	bmp, err := gozxing.NewBinaryBitmapFromImage(img)
	if err != nil {
		return
	}
	slog.Info("bmp..")
	// 3. 用二进制位图解码获取gozxing的二维码对象
	res, err := qrcode.NewQRCodeReader().Decode(bmp, nil)
	if err != nil {
		return
	}
	slog.Info("decode2..")
	// 4. 用结果来获取go-qrcode对象（注意这里我用了库的别名）
	qr, err := goQrcode.New(res.String(), goQrcode.High)
	if err != nil {
		return
	}
	slog.Info("printCode..")
	// 5. 输出到标准输出流
	fmt.Println(qr.ToSmallString(false))

	return
}

func (tr *TaskRunner) block() chromedp.ActionFunc {
	return func(ctx context.Context) error {
		slog.Infof("blocking...")
		<-tr.blockSig
		return nil
	}
}

func (tr *TaskRunner) wait(sec int) chromedp.ActionFunc {
	return func(ctx context.Context) error {
		Countdown(sec)
		return nil
	}
}

// 保存Cookies
func (tr *TaskRunner) saveCookies() chromedp.ActionFunc {
	return tr._saveCookies(false)
}

func (tr *TaskRunner) saveCookiesAsFile() chromedp.ActionFunc {
	return tr._saveCookies(true)
}

func (tr *TaskRunner) _saveCookies(isDumpFile bool) chromedp.ActionFunc {
	return func(ctx context.Context) (err error) {
		// cookies的获取对应是在devTools的network面板中
		// 1. 获取cookies
		cookies, err := network.GetAllCookies().Do(ctx)
		if err != nil {
			return
		}

		// 2. 序列化
		cookiesData, err := network.GetAllCookiesReturns{Cookies: cookies}.MarshalJSON()
		if err != nil {
			return
		}

		tr.credential = cookiesData

		if isDumpFile {
			// 3. 存储到临时文件
			if err = ioutil.WriteFile("cookies.tmp", cookiesData, 0755); err != nil {
				return
			}

		}

		// fmt.Println(string(cookiesData))
		return
	}
}

// 保存Cookies
func (tr *TaskRunner) loadCookies() chromedp.ActionFunc {
	return tr._loadCookies(false)
}

func (tr *TaskRunner) loadCookiesFromFile() chromedp.ActionFunc {
	return tr._loadCookies(true)
}

// 加载Cookies
func (tr *TaskRunner) _loadCookies(isFromDumpFile bool) chromedp.ActionFunc {
	return func(ctx context.Context) (err error) {
		// 如果cookies临时文件不存在则直接跳过
		if _, _err := os.Stat("cookies.tmp"); os.IsNotExist(_err) {
			slog.Info("cookies临时文件 skip..")
			return
		}
		cookiesData := tr.credential
		if isFromDumpFile {
			// 如果存在则读取cookies的数据
			cookiesData, err = ioutil.ReadFile("cookies.tmp")
			if err != nil {
				return
			}
		}

		// 反序列化
		cookiesParams := network.SetCookiesParams{}
		if err = cookiesParams.UnmarshalJSON(cookiesData); err != nil {
			return
		}

		// 设置cookies
		return network.SetCookies(cookiesParams.Cookies).Do(ctx)
	}
}

func byPass() chromedp.ActionFunc {
	const script = `(function(w, n, wn) {
		// Pass the Webdriver Test.
		Object.defineProperty(n, 'webdriver', {
			get: () => false,
		});
	
		// Pass the Plugins Length Test.
		// Overwrite the plugins property to use a custom getter.
		Object.defineProperty(n, 'plugins', {
			// This just needs to have length > 0 for the current test,
			// but we could mock the plugins too if necessary.
			get: () => [1, 2, 3, 4, 5],
		});
	
		// Pass the Languages Test.
		// Overwrite the plugins property to use a custom getter.
		Object.defineProperty(n, 'languages', {
			get: () => ['en-US', 'en'],
		});
	
		// Pass the Chrome Test.
		// We can mock this in as much depth as we need for the test.
		w.chrome = {
			runtime: {},
		};
	
		// Pass the Permissions Test.
		const originalQuery = wn.permissions.query;
		return wn.permissions.query = (parameters) => (
			parameters.name === 'notifications' ?
				Promise.resolve({ state: Notification.permission }) :
				originalQuery(parameters)
		);
	
	})(window, navigator, window.navigator);`
	return func(ctx context.Context) error {
		var err error
		_, err = page.AddScriptToEvaluateOnNewDocument(script).Do(ctx)
		if err != nil {
			return err
		}
		return nil
	}
}

func preTasks() chromedp.Tasks {
	return chromedp.Tasks{
		network.Enable(),
		byPass(),
	}
}

// 检查是否登陆
// func checkLoginStatus() chromedp.ActionFunc {
// 	return func(ctx context.Context) (err error) {
// 		var url string
// 		if err = chromedp.Evaluate(`window.location.href`, &url).Do(ctx); err != nil {
// 			slog.Info("234没有登录..")
// 			return
// 		}
// 		if strings.Contains(url, "https://account.wps.cn/usercenter/apps") {
// 			slog.Debug("已经使用cookies登陆")
// 			chromedp.Stop()
// 		}
// 		slog.Info("没有登录..")
// 		return
// 	}
// }

// func _runTasksWithTimeout(tasks chromedp.Tasks, timeout time.Duration) {
// 	ctx, cancel := chromedp.NewExecAllocator(
// 		context.Background(),
// 		markChromeDpFlags()...,
// 	)
// 	defer cancel()
// 	ctx, cancel = context.WithTimeout(ctx, timeout)
// 	defer cancel()
// 	ctx, cancel = chromedp.NewContext(
// 		ctx,
// 		chromedp.WithLogf(slog.Debugf),
// 	)
// 	defer cancel()

// 	// ensure that the browser process is started
// 	if err := chromedp.Run(ctx); err != nil {
// 		slog.Fatal(err)
// 		chromedp.Stop()
// 	}

// 	// listenForNetworkEvent(ctx)
// 	err := chromedp.Run(ctx,
// 		preTasks(),
// 		tasks,
// 	)
// 	if err != nil {
// 		slog.Fatal(err)
// 		chromedp.Stop()
// 	}

// }
